/**
 * 
 */
package lists;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
import tester.TestMethod;
import tester.Tester;

/**
 * <h1> Title : MyArrayList </h1>  
 * <p> Description : This is like an <code>ArrayList</code> but with some functional style methods. </p> 
 * 
 * @author Adam Blackwell & Ryan Cassidy
 * @since February 20th 2009
 * @version 1.5
 */
public class MyArrayList<T> extends List<T> implements Cloneable{
  /**
   * <p> Default Value - Ignore. </p> 
   */
  private static final long serialVersionUID = 1L;
  
  /** <p> Constructor : </p> */
  public MyArrayList() {}
  
  // =================================================================
  // Functions:
  // =================================================================
  
  /* (non-Javadoc)
   * @see lists.List#count(lists.UnaryFunction)
   */
  @Override public int count(UnaryFunction<Boolean, T> f) {
    int count = 0;
    /* Iterates Through the list. */
    for (int i = 0; i < this.size(); i++) {
      /* Is this an item we want to count? */
      if (f.f(this.get(i))) {
        /* Increment count. */
        count += 1;
      }
    }
    return count;
  }
  
  /* (non-Javadoc)
   * @see lists.List#filter(lists.UnaryFunction)
   */
  @Override public List<T> filter(UnaryFunction<Boolean, T> f) {
    List<T> l = this;
    /* Iterates Through the list. */
    for (int i = 0; i < this.size(); i++) {
      /* Do we not want to keep this element? */
      if (!f.f(l.get(i))) {
        /* Remove it. */
        l.remove(i);
      }
    }
    return l;
  }
  
  /*`
   * (non-Javadoc)
   * @see lists.List#map(lists.UnaryFunction)
   */
  @Override public <R> List<R> map(UnaryFunction<R, T> f) {
    List<R> out = new MyArrayList<R>();
    /* Iterates Through the list. */
    for (T t : this) {
      /* Adds the resultant of the current element to the output list. */
      out.add(f.f(t));
    }
    /* Return the finished output list. */
    return out;
  }
  
  /* (non-Javadoc)
   * @see lists.List#ormap(lists.UnaryFunction)
   */
  @Override public boolean ormap(UnaryFunction<Boolean, T> f) {
    /* If it is an empty list return false. */
    if (this.size() == 0) {
      return false;
      /* If it is a single element list apply the predicate to the
       * single object. */
    } else if (this.size() == 1) {
      return f.f(this.get(0));
      /* Iterates through the list. */
    } else {
      for (int i = 0; i < this.size(); i++) {
        /* If the result of the function on the current element is false. */
        if (!f.f(this.get(i))) {
          /* Resturn false. */
          return false;
        }
      }
      /* One you have gone through, return true. */
      return true;
    }
  }
  
  /*
   * (non-Javadoc)
   * @see lists.List#fold(lists.BinaryFunction, java.lang.Object)
   */
  @Override public <R> R foldr(BinaryFunction<R, T, R> f, R base) {
    /* Creates a list to be changed and passed into the recursion. */
    List<T> l = new MyArrayList<T>();
    /* Calls the passed in functions "f" with the first and rest of the list. */
    if (l.size() > 0) {
      /* Assigns the first element to first. */
      T first = l.remove(0);
      /* returns the resultant of the fold function on the current first element 
       * and the recursive call for the rest of the list */
      return f.f(first, l.foldr(f, base));
    } else {
      /* Until you get to the end, then return the base, which you have accumulated. */
      return base;
    }
  }
  
  /* (non-Javadoc)
   * @see lists.List#insert(lists.BinaryFunction, java.lang.Object)
   */
  @Override public List<T> insert(BinaryFunction<Boolean, T, T> f, T t) {
    /* Creates a list to be changed and passed into the recursion. */
    List<T> l = this;
    /* Iterates Through the list. */
    for (int i = 0; i < this.size(); i++) {
      /* If you should insert. */
      if (f.f(t, l.get(i))) {
        /* Insert. */
        l.add(i, t);
      }
    }
    /* Else don't. */
    return l;
  }
  
  /* (non-Javadoc)
   * @see lists.List#sort(lists.BinaryFunction)
   */
  @Override public List<T> sort(BinaryFunction<Boolean, T, T> f) {
    /* the output list. */
    List<T> out = new MyArrayList<T>();
    /* If it is an empty list or a one element list return it. */
    if (this.size() == 0 || this.size() == 1) {
      return this;
    /* Else Iterate through the list. */
    } else {
      for (int i = 0; i < this.size(); i++) {
        /* Insert the surrent element where it should go. */
        out.insert(f, this.get(i));
      }
    }
    /* Return the sorted list. */
    return out;
  }
  
  /*
   * (non-Javadoc)
   * @see lists.List#toString()
   */
  @Override public String toString() {
    /* The output string */
    String out = "(";
    /* Creates a string out of all the elements. */
    for (int i = 0; i < this.size(); i++) {
      /* Insert the current element where it should go. */
      out = out + ", " + this.get(i).toString();
    }
    return out + ")";
  }
  
  // =================================================================
  // Tests:
  // =================================================================
  
  @TestMethod public void testCount(Tester t) {
    t.checkExpect(true);
  }
  
  @TestMethod public void testFilter(Tester t) {
    t.checkExpect(true);
  }
  
  @TestMethod public void testFoldr(Tester t) {
    t.checkExpect(true);
  }
  
  @TestMethod public void testInsert(Tester t) {
    t.checkExpect(true);
  }
  
  @TestMethod public void testMap(Tester t) {
    t.checkExpect(true);
  }
  
  @TestMethod public void testOrMap(Tester t) {
    t.checkExpect(true);
  }
  
  @TestMethod public void testSort(Tester t) {
    t.checkExpect(true);
  }
  
//  @TestMethod public void test8(Tester t) {
//    t.checkExpect(true);
//  }
//  
//  @TestMethod public void test9(Tester t) {
//    t.checkExpect(true);
//  }
}
